﻿using System.Linq;

namespace Magellan.Routing
{
    public class DefaultRouteValidator : IRouteValidator
    {
        public virtual RouteValidationResult Validate(ParsedRoute route)
        {
            var segments = route.Segments;

            // Shortcut - no need to check any rules for an empty route
            if (segments.Length == 0)
            {
                return RouteValidationResult.Successful();
            }

            // Unrecognizable segments
            var unrecognized = segments.Where(x => !(x is CatchAllParameterSegment || x is ParameterSegment || x is LiteralSegment)).ToList();
            if (unrecognized.Count > 0)
            {
                return RouteValidationResult.Failure(string.Format("Unrecognized segment types were found. If using a custom segment type, please replace the IRouteValidityChecker to enforce your own route validation rules. Unrecognized types: {0}", string.Join(", ", unrecognized.Select(x => "'" + x.GetType().Name + "'").ToArray())));
            }

            // More than one catch-all segment
            var catchAll = segments.OfType<CatchAllParameterSegment>().ToList();
            if (catchAll.Count > 1)
            {
                return RouteValidationResult.Failure(string.Format("A route cannot have more than one catch-all parameter. Catch all parameters: {0}", string.Join(", ", catchAll.Select(x => "'" + x.ParameterName + "'"))));    
            }

            // Catch-all that isn't at end
            if (catchAll.Count == 1 && segments.Last() != catchAll.Single())
            {
                return RouteValidationResult.Failure(string.Format("Catch-all parameters may only appear at the end of a route. Catch all parameter: '{0}'", catchAll.Single().ParameterName));    
            }
            
            // Parameter names must be unique
            var parameterNames = segments.OfType<ParameterSegment>().Select(x => x.ParameterName).Concat(
                segments.OfType<CatchAllParameterSegment>().Select(x => x.ParameterName)
                ).Select(x => x.ToLowerInvariant());
            var duplicateNames = parameterNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(x => x.Key).ToList();
            if (duplicateNames.Count > 0)
            {
                return RouteValidationResult.Failure(string.Format("The same parameter name cannot be used twice within a route. The following parameters appeared more than once: {0}", string.Join(", ", duplicateNames.Select(x => "'" + x + "'"))));                    
            }            

            return RouteValidationResult.Successful();
        }
    }
}